﻿// -----------------------------------------------------------------------
// <copyright file="PictureResult.cs" company="EPAM Systems">
////Copyright (c) EPAM Systems. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------
using System;
using System.Configuration;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Web;
using System.Web.Mvc;
using Epam.PhotoGallery.Bussines.CacheManager;
using Epam.PhotoGallery.Bussines.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Caching;
using Microsoft.Practices.EnterpriseLibrary.Caching.Expirations;
using Epam.PhotoGallery.Interfaces.Managers;
using Epam.PhotoGallery.Site.Models;

namespace Epam.PhotoGallery.Site.Models
{


	/// <summary>
	/// Picture result
	/// </summary>
	public class PictureResult : ActionResult
	{
		#region Constants

		private const string ActionRouteKey = "action";

		private const string GetCoverRouteValue = "GetCover";

		private const string GetImageRouteValue = "GetImage";

		private const string GetControlImageValue = "GetControlImage";

		private const int OneDay = 86400; // seconds un one day.

		private const int t = 3; // the average time viewing one picture in seconds.

		#endregion

		#region Privat fields

		/// <summary>
		/// string filePath
		/// </summary>
		private string filePath;

		/// <summary>
		/// int deskHeight
		/// </summary>
		private int clientHeight;

		/// <summary>
		/// int deskWidth
		/// </summary>
		private int clientWidth;

		private IEpamGaleryCacheManager cache;

		#endregion

		#region Constructor

		/// <summary>
		/// Initializes a new instance of the <see cref="PictureResult"/> class.
		/// </summary>
		/// <param name="filePath">The file path.</param>
		/// <param name="w">The w.</param>
		/// <param name="h">The h.</param>
		public PictureResult(string filePath, int w, int h)
		{
			this.filePath = filePath;
			this.clientHeight = h - 120;
			this.clientWidth = w - 80;
		}

		#endregion

		#region Private properties

		/// <summary>
		/// Gets the cache.
		/// </summary>
		public IEpamGaleryCacheManager Cache
		{
			get
			{
				if (this.cache == null)
				{
					this.cache = new CoverCacheManager(typeof(PictureResult));
				}

				return this.cache;
			}
		}

		#endregion

		#region Overriden methods

		/// <summary>
		/// Enables processing of the result of an action method by a custom type that inherits from the <see cref="T:System.Web.Mvc.ActionResult"/> class.
		/// </summary>
		/// <param name="context">The context in which the result is executed. The context information includes the controller, HTTP content, request context, and route data.</param>
		public override void ExecuteResult(ControllerContext context)
		{
			HttpResponseBase response = context.HttpContext.Response;
			byte[] result = null;
			string cacheKey = string.Empty;
			int zoom = 0;

			switch ((string)context.RouteData.Values[ActionRouteKey])
			{
				case GetCoverRouteValue:
					cacheKey = GetCoverRouteValue;
					zoom = GetCurrentZoom(GetCoverRouteValue);
					result = GetDataFromCache(cacheKey, zoom, AddCoverToCache);
					if (result == null)
					{
						result = ProcessImage(cacheKey, zoom, AddCoverToCache);
					}
					break;
				case GetImageRouteValue:
					cacheKey = GetImageRouteValue;
					zoom = GetCurrentZoom(GetImageRouteValue);
					result = GetDataFromCache(cacheKey, zoom, AddImageToCache);
					if (result == null)
					{
						result = ProcessImage(cacheKey, zoom, AddImageToCache);
					}
					break;
				case GetControlImageValue:
					result = GalleryControlImageResizer();
					break;
				default:
					throw new NotSupportedException("This action is not supported");
			}

			response.ContentType = "image/jpeg";
			response.OutputStream.Write(result, 0, result.Length);
			response.End();
		}

		#endregion

		private byte[] GalleryControlImageResizer()
		{
			byte[] result = null;

			Image image = null;

			using (var stream = File.OpenRead(this.filePath))
			{
				image = new Bitmap(stream);
			}
			int imageHeight = image.Height;
			int imageWidth = image.Width;

			double aspectRatioImage = (double)imageHeight / (double)imageWidth;
			double aspectRatioCleint = (double)this.clientHeight / (double)this.clientWidth;


			if (aspectRatioImage > aspectRatioCleint)
			{
				imageWidth = this.clientWidth;
				imageHeight = (int)Math.Round(imageWidth * aspectRatioImage);

			}
			else
			{
				imageHeight = this.clientHeight;
				imageWidth = (int)Math.Round(imageHeight / aspectRatioImage);
			}
			//resizing
			Image resultImage = new Bitmap(image, imageWidth, imageHeight);
			using (Graphics g = Graphics.FromImage(resultImage))
			{
				g.InterpolationMode = InterpolationMode.HighQualityBicubic;
				g.DrawImage(image, 0, 0, resultImage.Width, resultImage.Height);
			}
			image.Dispose();

			//cropping
			int x = (int)Math.Floor((double)((imageWidth - this.clientWidth) / 2));
			int y = (int)Math.Floor((double)((imageHeight - this.clientHeight) / 2));
			Rectangle cropRect = new Rectangle(x, y, this.clientWidth,this.clientHeight);
			resultImage=CropImage(resultImage, cropRect);
			
			//Bitmap cropBlock = new Bitmap(cropRect.Width, cropRect.Height);
			//using (Graphics g = Graphics.FromImage(cropBlock))
			//{
			//    g.DrawImage(resultImage, new Rectangle(0, 0,this.clientWidth , this.clientHeight), cropRect, GraphicsUnit.Pixel);
			//}
			result = ImageToByteArray(resultImage);
			return result;
		}

		private static Image CropImage(Image img, Rectangle cropArea)
		{
			Bitmap image = new Bitmap(img);
			Bitmap crop = image.Clone(cropArea,
			image.PixelFormat);
			return (Image)(crop);
		}

		/// <summary>
		/// Gets the current zoom.
		/// </summary>
		/// <param name="routeValue">The route value.</param>
		/// <returns>
		/// Return current zoom.
		/// </returns>
		private static int GetCurrentZoom(string routeValue)
		{
			ZoomPictureSection zoom = (ZoomPictureSection)ConfigurationManager.GetSection(ZoomPictureSection.ZoomPictureSectionName);

			int currentZoom = 0;

			if (routeValue.Equals(GetCoverRouteValue))
			{
				currentZoom = zoom.CoverZoom;
			}
			else
				if (routeValue.Equals(GetImageRouteValue))
				{
					currentZoom = zoom.FancyZoom;
				}

			return currentZoom;
		}

		#region Internal implementation

		/// <summary>
		/// Adds the image to cache.
		/// </summary>
		/// <param name="cacheKey">The cache key.</param>
		/// <param name="isNeedAddToCache">if set to <c>true</c> [is need add to cache].</param>
		/// <param name="imageContent">The image.</param>
		/// <param name="slideMinute">The slide minute.</param>
		private void AddImageToCache(string cacheKey, byte[] imageContent)
		{
			int slideMinute = (Directory.GetFiles(Path.GetDirectoryName(this.filePath))).Length * t;
			this.Cache.Add(cacheKey, imageContent, (int)slideMinute);
		}

		/// <summary>
		/// Adds the cover to cache.
		/// </summary>
		/// <param name="cacheKey">The cache key.</param>
		/// <param name="cover">The cover.</param>
		private void AddCoverToCache(string cacheKey, byte[] coverContent)
		{
			this.Cache.Add(cacheKey, coverContent, OneDay);
		}

		/// <summary>
		/// Processes the context.
		/// </summary>
		/// <param name="casheKey">The cashe key.</param>
		/// <param name="currentZoom">The current zoom.</param>
		/// <param name="isNeedAddToCache">if set to <c>true</c> [is need add to cache].</param>
		/// <returns>
		/// Return array of bytes.
		/// </returns>
		private byte[] GetDataFromCache(string casheKey, int currentZoom, Action<string, byte[]> action)
		{
			casheKey = string.Concat(casheKey, this.filePath);

			var imageContent = this.Cache.GetItem<byte[]>(casheKey);

			if (imageContent != null)
			{
				ZoomPictureSection zoomPicture = (ZoomPictureSection)ConfigurationManager.GetSection(ZoomPictureSection.ZoomPictureSectionName);
				int averageHeight = zoomPicture.AverageHeight;
				int averageWidth = zoomPicture.AverageWidth;

				Image image = null;

				if (clientWidth > averageWidth || clientHeight > averageHeight)
				{
					var stream = new MemoryStream(imageContent);
					image = Image.FromStream(stream);
					image = ResizeImage(image, clientWidth, clientHeight, currentZoom);

					imageContent = ImageToByteArray(image);

					action(casheKey, imageContent);
				}
			}

			return imageContent;
		}

		/// <summary>
		/// Processes the image.
		/// </summary>
		/// <param name="casheKey">The cashe key.</param>
		/// <param name="currentZoom">The current zoom.</param>
		/// <param name="action">The action.</param>
		/// <returns>Byte array</returns>
		private byte[] ProcessImage(string casheKey, int currentZoom, Action<string, byte[]> action)
		{
			byte[] result = null;

			ZoomPictureSection zoomPicture = (ZoomPictureSection)ConfigurationManager.GetSection(ZoomPictureSection.ZoomPictureSectionName);
			int averageHeight = zoomPicture.AverageHeight;
			int averageWidth = zoomPicture.AverageWidth;

			Image image = null;

			casheKey = string.Concat(casheKey, this.filePath);

			using (var stream = File.OpenRead(this.filePath))
			{
				image = new Bitmap(stream);
			}

			if (clientWidth <= averageWidth || clientHeight <= averageHeight)
			{
				image = ResizeImage(image, averageWidth, averageHeight, currentZoom);
			}
			else
			{
				image = ResizeImage(image, clientWidth, clientHeight, currentZoom);
			}

			result = ImageToByteArray(image);

			action(casheKey, result);

			return result;
		}

		/// <summary>
		/// Images to byte array.
		/// </summary>
		/// <param name="image">The image.</param>
		/// <returns>
		/// Return array of bytes.
		/// </returns>
		private byte[] ImageToByteArray(Image image)
		{
			MemoryStream ms = new MemoryStream();
			image.Save(ms, ImageFormat.Jpeg);
			return ms.ToArray();
		}

		/// <summary>
		/// Resizes the image.
		/// </summary>
		/// <param name="Image">The stream image.</param>
		/// <param name="maxWidth">Width of the max.</param>
		/// <param name="maxHeight">Height of the max.</param>
		/// <returns>
		/// Return image file as Bitmap.
		/// </returns>
		private Image ResizeImage(Image originalImage, int targetWidth, int targetHeigth, int currentZoom)
		{
			targetWidth = (int)Math.Floor((decimal)(currentZoom * targetWidth / 100));
			targetHeigth = (int)Math.Floor((decimal)(currentZoom * targetHeigth / 100));

			int newWidth = originalImage.Width;
			int newHeight = originalImage.Height;
			double aspectRatioImage = (double)newWidth / (double)newHeight;
			double aspectRatioScreen = (double)targetWidth / (double)targetHeigth;

			if (aspectRatioImage < aspectRatioScreen)
			{
				newHeight = targetHeigth;
				newWidth = (int)Math.Round(newHeight * aspectRatioImage);
			}
			else
			{
				newWidth = targetWidth;
				newHeight = (int)Math.Round(newWidth / aspectRatioImage);
			}
			Image newImage = new Bitmap(originalImage, newWidth, newHeight);
			using (Graphics g = Graphics.FromImage(newImage))
			{
				g.InterpolationMode = InterpolationMode.HighQualityBicubic;
				g.DrawImage(originalImage, 0, 0, newImage.Width, newImage.Height);
			}
			originalImage.Dispose();

			return newImage;
		}

		#endregion
	}
}